﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Derbster.Domain
{
    public class OfficialRoster : IEnumerable<RosteredOfficial>, IEquatable<OfficialRoster>
    {
        private CommandManager _commandManager;

        [Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int OfficialRosterId { get; internal set; }
        public string Name { get; set; }
        public HashSet<RosteredOfficial> SkatingOfficials { get; internal set; }
        public HashSet<RosteredOfficial> NonSkatingOfficials { get; internal set; }
       
        [ContractVerification(false)]
        internal OfficialRoster()
        {
            _commandManager = new CommandManager();
        }

        public OfficialRoster(string name)
        {
            Name = name;
            SkatingOfficials = new HashSet<RosteredOfficial>();
            NonSkatingOfficials = new HashSet<RosteredOfficial>();
            _commandManager = new CommandManager();
        }

        public RosteredOfficial AddSkatingOfficial(Official official, OfficialPosition position)
        {
            Contract.Requires(official != null);
            Contract.Requires(
                position == OfficialPosition.Alternate 
                || position == OfficialPosition.HeadReferee
                || position == OfficialPosition.InsidePackReferee 
                || position == OfficialPosition.JamReferee
                || position == OfficialPosition.OutsidePackReferee);
            Contract.Requires(SkatingOfficials != null);
            Contract.Requires(SkatingOfficials.All(ro => ro.Official != official));
            Contract.Ensures(SkatingOfficials.Any(ro => ro.Official == official && ro.OfficialPosition == position));

            var rosteredOfficial = DoAddSkatingOfficial(official, position);

            if (_commandManager != null)
            {
                _commandManager.Add(
                        () => DoRemoveSkatingOfficial(official),
                        () => DoAddSkatingOfficial(official, position)
                    );
            }

            return rosteredOfficial;
        }

        private RosteredOfficial DoAddSkatingOfficial(Official official, OfficialPosition position)
        {
            Contract.Requires(official != null);
            Contract.Requires(
                position == OfficialPosition.Alternate
                || position == OfficialPosition.HeadReferee
                || position == OfficialPosition.InsidePackReferee
                || position == OfficialPosition.JamReferee
                || position == OfficialPosition.OutsidePackReferee);
            Contract.Requires(SkatingOfficials != null);
            Contract.Requires(SkatingOfficials.All(ro => ro.Official != official));
            Contract.Ensures(SkatingOfficials.Any(ro => ro.Official == official && ro.OfficialPosition == position));

            var rosteredOfficial = new RosteredOfficial(official, position);
            SkatingOfficials.Add(rosteredOfficial);
            return rosteredOfficial;
        }

        public void RemoveSkatingOfficial(Official official)
        {
            Contract.Requires(official != null);
            Contract.Requires(SkatingOfficials != null);
            Contract.Requires(SkatingOfficials.Count > 0);
            Contract.Requires(SkatingOfficials.Any(ro => ro.Official == official));
            Contract.Ensures(SkatingOfficials.All(ro => ro.Official != official));

            var position = SkatingOfficials.Single(ro => ro.Official == official).OfficialPosition;
            DoRemoveSkatingOfficial(official);

            if (_commandManager != null)
            {
                _commandManager.Add(
                        () => DoAddSkatingOfficial(official, position),
                        () => DoRemoveSkatingOfficial(official)
                    );
            }
        }

        private void DoRemoveSkatingOfficial(Official official)
        {
            Contract.Requires(official != null);
            Contract.Requires(SkatingOfficials != null);
            Contract.Requires(SkatingOfficials.Count > 0);
            Contract.Requires(SkatingOfficials.Any(ro => ro.Official == official));
            Contract.Ensures(SkatingOfficials.All(ro => ro.Official != official));

            SkatingOfficials.RemoveWhere(ro => ro.Official == official);
        }

        public RosteredOfficial AddNonSkatingOfficial(Official official, OfficialPosition position)
        {
            Contract.Requires(official != null);
            Contract.Requires(
                position != OfficialPosition.HeadReferee
                && position != OfficialPosition.InsidePackReferee
                && position != OfficialPosition.JamReferee
                && position != OfficialPosition.OutsidePackReferee);
            Contract.Requires(NonSkatingOfficials != null);
            Contract.Requires(NonSkatingOfficials.Count >= 0);
            Contract.Requires(NonSkatingOfficials.All(ro => ro.Official != official));
            Contract.Ensures(NonSkatingOfficials.Any(ro => ro.Official == official && ro.OfficialPosition == position));

            var rosteredOfficial = DoAddNonSkatingOfficial(official, position);

            if (_commandManager != null)
            {
                _commandManager.Add(
                        () => DoRemoveNonSkatingOfficial(official),
                        () => DoAddNonSkatingOfficial(official, position)
                    );
            }

            return rosteredOfficial;
        }

        private RosteredOfficial DoAddNonSkatingOfficial(Official official, OfficialPosition position)
        {
            Contract.Requires(official != null);
            Contract.Requires(
               position != OfficialPosition.HeadReferee
               && position != OfficialPosition.InsidePackReferee
               && position != OfficialPosition.JamReferee
               && position != OfficialPosition.OutsidePackReferee);
            Contract.Requires(NonSkatingOfficials != null);
            Contract.Requires(NonSkatingOfficials.Count >= 0);
            Contract.Requires(NonSkatingOfficials.All(ro => ro.Official != official));
            Contract.Ensures(NonSkatingOfficials.Any(ro => ro.Official == official && ro.OfficialPosition == position));

            var rosteredOfficial = new RosteredOfficial(official, position);
            NonSkatingOfficials.Add(rosteredOfficial);
            return rosteredOfficial;
        }

        public void RemoveNonSkatingOfficial(Official official)
        {
            Contract.Requires(official != null);
            Contract.Requires(NonSkatingOfficials != null);
            Contract.Requires(NonSkatingOfficials.Count > 0);
            Contract.Requires(NonSkatingOfficials.Any(ro => ro.Official == official));
            Contract.Ensures(NonSkatingOfficials.All(ro => ro.Official != official));

            var position = NonSkatingOfficials.Single(ro => ro.Official == official).OfficialPosition;

            DoRemoveNonSkatingOfficial(official);

            if (_commandManager != null)
            {
                _commandManager.Add(
                        () => DoAddNonSkatingOfficial(official, position),
                        () => DoRemoveNonSkatingOfficial(official)
                    );
            }
        }

        private void DoRemoveNonSkatingOfficial(Official official)
        {
            Contract.Requires(official != null);
            Contract.Requires(NonSkatingOfficials != null);
            Contract.Requires(NonSkatingOfficials.Count > 0);
            Contract.Requires(NonSkatingOfficials.Any(ro => ro.Official == official));
            Contract.Ensures(NonSkatingOfficials.All(ro => ro.Official != official));

            NonSkatingOfficials.RemoveWhere(ro => ro.Official == official);
        }

        public IEnumerator<RosteredOfficial> GetEnumerator()
        {
            foreach (var official in SkatingOfficials)
            {
                yield return official;
            }
            foreach (var official in NonSkatingOfficials)
            {
                yield return official;
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public bool Equals(OfficialRoster other)
        {
            if (other == null) return false;

            return other.OfficialRosterId == OfficialRosterId;
        }
    }
}
